package com.fsm.utils;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.Collections;

/*******************
 *@Describe
 *@author 封神梦
 *@Time 2023/6/13
 *@Company http://com.yiview.com
 ****************/

public class RedisDLock   {
    private static final Logger LOG = LogManager.getLogger(RedisDLock.class);
    private final String lockName;
    private final long lockDuration;
    private final String lockSubject;
    private final Jedis jedis;

    public RedisDLock(String lockName, long lockDuration, String lockSubject, Jedis jedis) {
        this.lockName = lockName;
        this.lockDuration = lockDuration;
        this.lockSubject = lockSubject;
        this.jedis = jedis;
    }

    public void lock() {
        tryLock(Long.MAX_VALUE);
    }
    public boolean tryLock() {
        return tryLock(0);
    }
    public boolean tryLock(long waitTimeout) {
        long startTime = System.currentTimeMillis();
        while (true) {
            try {
                // 尝试加锁。等同于 SETNX + EXPIRE，通过设置一个属性值完成加锁过程
                if (jedis.set(lockName, //  锁名
                        lockSubject,  //  锁申请人
                        "NX",           //  锁名不存在时插入
                        "PX",           //  锁的有效时长的时间单位为millisecond
                        lockDuration    //  锁的有效时长
                ) != null) {
                    // 加锁成功
                    return true;
                }
                // 若加锁不成功，获取锁的拥有者。加锁不成功有可能是申请者已执有此锁，也可能是其他人执有此锁
                String lockOwner = jedis.get(lockName);
                // 若锁执有者与锁申请者相同，则返回申请者已占用此锁
                if (lockSubject.equals(lockOwner)) {
                    return true;
                }
                // 若锁未被占用，则再次申请锁
                if (lockOwner == null) {
                    continue;
                }
                if (System.currentTimeMillis() - startTime < waitTimeout) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ie) {
                    }
                    continue;
                }
                // 返回锁已被其他申请主体占用
                return false;
            } catch (Exception e) {
                LOG.error("tryLock error", e);
                if (System.currentTimeMillis() - startTime < waitTimeout) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ie) {
                    }
                    continue;
                }
                return false;
            }
        }
    }
    // 使用transaction解锁
    public void unlock1() {
        try {
            jedis.watch(lockName); //  添加对锁的监控，若锁的相关属性发生变化，则整个事务将不执行
            String lockOwner = jedis.get(lockName);
            if (lockSubject.equals(lockOwner)) {
                // 若锁未超时，则del操作会删除锁记录
                // 若锁已超时，当前锁无执有者，则del操作不会造成影响
                // 若锁已超时，当前锁被其他人执有，由于watch监控到锁的属性有变化，则事务中的del操作不会真正执行
                Transaction tx = jedis.multi();
                tx.del(lockName);
                tx.exec();
            } else {
                jedis.unwatch(); // 若不再执有锁，则放开监控
            }
        } catch (Exception e) {
            LOG.error("unlock error", e);
        }
    }
    // 使用lua script解锁
    public void unlock() {
        try {
            String script =
                    "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
            jedis.eval(script, Collections.singletonList(lockName),Collections.singletonList(lockSubject));
        } catch (Exception e) {
            LOG.error("unlock error", e);
        }
    }
}